<?php
/**
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * This license is also available via the world-wide-web at
 * http://itdapps.ncsu.edu/bsd.txt
 *
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to itappdev@ncsu.edu so we can send you a copy immediately.
 *
 * @package    Oauth_Request
 * @category   Library
 * @copyright  Copyright (c) 2007 NC State University Office of Information Technology
 * @license    http://itdapps.ncsu.edu/bsd.txt  BSD License
 * @version    SVN: $Id: $
 */

/**
 * 
 *
 * @package    Oauth_Request
 * @category   Oauth_
 * @copyright  Copyright (c) 2007 NC State University Office of Information Technology
 */
class Google_Oauth_Request
{
    
    private $_parameters;
    private $_httpMethod;
    private $_httpUrl;
    
    // for debug purposes
    public $baseString;
    public static $version = '1.0';
    
    function __construct($httpMethod, $httpUrl, $parameters = NULL) {
        
        @$parameters or $parameters = array();
        $this->_parameters = $parameters;
        $this->_httpMethod = $httpMethod;
        $this->_httpUrl = $httpUrl;
    }
    
    public function setParameter($name, $value) {
        $this->_parameters[$name] = $value;
    }
    
    public function getParameter($name) {
        return isset($this->_parameters[$name]) ? $this->_parameters[$name] : null;
    }
    
    public function getParameters() {
        return $this->_parameters;
    }
    
    /**
     * Returns the normalized parameters of the request
     * 
     * This will be all (except Oauth__signature) parameters,
     * sorted first by key, and if duplicate keys, then by
     * value.
     *
     * The returned string will be all the key=value pairs
     * concated by &.
     * 
     * @return string
     */
    public function getSignableParameters() {
        // Grab all parameters
        $params = $this->_parameters;       
            
        // Remove Oauth__signature if present
        if (isset($params['oauth_signature'])) {
            unset($params['oauth_signature']);
        }
            
        // Urlencode both keys and values
        $keys = Oauth_Util::urlencodeRfc3986(array_keys($params));
        $values = Oauth_Util::urlencodeRfc3986(array_values($params));
        $params = array_combine($keys, $values);
        
        // Sort by keys (natsort)
        uksort($params, 'strcmp');
        
        // Generate key=value pairs
        $pairs = array();
        foreach ($params as $key =>$value ) {
            if (is_array($value)) {
                // If the value is an array, it's because there are multiple 
                // with the same key, sort them, then add all the pairs
                natsort($value);
                foreach ($value as $v2) {
                    $pairs[] = $key . '=' . $v2;
                }
            } else {
                $pairs[] = $key . '=' . $value;
            }
        }
            
        // Return the pairs, concated with &
        return implode('&', $pairs);
    }
    
    /**
     * Returns the base string of this request
     *
     * The base string defined as the method, the url
     * and the parameters (normalized), each urlencoded
     * and the concated with &.
     */
    public function getSignatureBaseString() {
        $parts = array(
            $this->getNormalizedHttpMethod(),
            $this->getNormalizedHttpUrl(),
            $this->getSignableParameters()
        );
                
        $parts = Oauth_Util::urlencodeRfc3986($parts);
        
        return implode('&', $parts);
    }
    
    /**
     * just uppercases the http method
     */
    public function getNormalizedHttpMethod() {
        return strtoupper($this->_httpMethod);
    }
    
    /**
     * parses the url and rebuilds it to be
     * scheme://host/path
     */
    public function getNormalizedHttpUrl() {
        $parts = parse_url($this->_httpUrl);
        
        $port = @$parts['port'];
        $scheme = $parts['scheme'];
        $host = $parts['host'];
        $path = @$parts['path'];
        
        $port or $port = ($scheme == 'https') ? '443' : '80';
        
        if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) {
            $host = "$host:$port";
        }
        
        return "$scheme://$host$path";
    }
    
    /**
     * builds a url usable for a GET request
     */
    public function toUrl() {
        $out = $this->getNormalizedHttpUrl() . "?";
        $out .= $this->toPostdata(false);
        return $out;
    }
    
    /**
     * builds a url usable for a GET request
     */
    public function toPostUrl() {
        $out = $this->getNormalizedHttpUrl() . "?";
        $out .= $this->toPostdata(false);
        return $out;
    }
    
    /**
     * builds the data one would send in a POST request
     *
     * TODO(morten.fangel):
     * this function might be easily replaced with http_build_query()
     * and corrections for rfc3986 compatibility.. but not sure
     */
    public function toPostdata($includeOauthParams = true) {
        $total = array();
        foreach ($this->_parameters as $k => $v) {
            
            if (substr($k, 0, 5) != "oauth" || $includeOauthParams) { 

                if (is_array($v)) {
                    foreach ($v as $va) {
                        $total[] = Oauth_Util::urlencodeRfc3986($k) . "[]=" . Oauth_Util::urlencodeRfc3986($va);
                    }
                } else {
                    $total[] = Oauth_Util::urlencodeRfc3986($k) . "=" . Oauth_Util::urlencodeRfc3986($v);
                }
                
            }            
        }
        
        $out = implode("&", $total);
        return $out;
    }
    
    /**
     * pretty much a helper function to set up the request
     */
    public static function fromConsumerAndToken($consumer, $token, $httpMethod, $httpUrl, $parameters=NULL) {
        @$parameters or $parameters = array();
        $defaults = array("oauth_version" => Oauth_Request::$version,
                          "oauth_nonce" => Google_Oauth_Request::generateNonce(),
                          "oauth_timestamp" => Google_Oauth_Request::generateTimestamp(),
                          "oauth_consumer_key" => $consumer->key);
        $parameters = array_merge($defaults, $parameters);
        
        if ($token) {
            $parameters['oauth_token'] = $token->key;
        }
        
        return new Google_Oauth_Request($httpMethod, $httpUrl, $parameters);
    }
    
    /**
     * builds the Authorization: header
     */
    public function toHeader() {

        $out = '';
        
        foreach ($this->_parameters as $k => $v) {
            
            if (substr($k, 0, 5) != "oauth") { 
                continue;
            }
            
            if (is_array($v)) { 
                throw new Oauth_Exception('Arrays not supported in headers');
            }
            
            if ($out != '') {
                $out .= ',';
            }
            
            $out .= Oauth_Util::urlencodeRfc3986($k) . '="' . Oauth_Util::urlencodeRfc3986($v) . '"';
        }
        return 'Authorization: OAuth ' . $out;
    }
    
    public function __toString() {
        return $this->toUrl();
    }
    
    
    public function signRequest($signatureMethod, $consumer, $token) {
        $this->setParameter("oauth_signature_method", $signatureMethod->getName());
        $signature = $this->buildSignature($signatureMethod, $consumer, $token);
        $this->setParameter("oauth_signature", $signature);
    }
    
    public function buildSignature($signatureMethod, $consumer, $token) {
        $signature = $signatureMethod->buildSignature($this, $consumer, $token);
        return $signature;
    }
    
    /**
     * util function: current timestamp
     */
    protected static function generateTimestamp() {
        return time();
    }
    
    /**
     * util function: current nonce
     */
    protected static function generateNonce() {
        $mt = microtime();
        $rand = mt_rand();
        
        return md5($mt . $rand); // md5s look nicer than numbers
    }
}